001 package net.sf.xdc.processing;
002
003 /*
004 * Copyright 2005-2006 Jens Voß.
005 *
006 * Licensed under the GNU Lesser General Public License (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://opensource.org/licenses/lgpl-license.php
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019 import java.io.File;
020 import java.io.FileInputStream;
021 import java.io.FileNotFoundException;
022 import java.io.FileOutputStream;
023 import java.io.IOException;
024 import java.io.InputStream;
025 import java.io.OutputStream;
026 import java.io.Writer;
027 import java.util.Properties;
028 import java.util.Set;
029 import java.util.HashSet;
030 import java.util.Iterator;
031 import java.util.Map;
032 import java.util.HashMap;
033 import java.util.Locale;
034 import java.util.StringTokenizer;
035 import java.util.ResourceBundle;
036 import java.util.Enumeration;
037 import java.util.List;
038 import java.util.Vector;
039 import java.nio.charset.Charset;
040 import java.nio.charset.UnsupportedCharsetException;
041
042 import javax.xml.transform.TransformerException;
043
044 import net.sf.xdc.util.Logging;
045 import net.sf.xdc.util.XmlException;
046 import net.sf.xdc.util.XmlUtils;
047 import net.sf.xdc.util.XslUtils;
048 import net.sf.xdc.util.IOUtils;
049 import net.sf.xdc.util.XPathUtils;
050 import org.apache.commons.cli.CommandLine;
051 import org.apache.log4j.Logger;
052 import org.w3c.dom.Document;
053 import org.w3c.dom.Element;
054 import org.w3c.dom.Node;
055 import org.w3c.dom.traversal.NodeIterator;
056
057 /**
058 * This class is responsible for processing all source files by applying the
059 * appropriate stylesheets to the sources specified during the invocation of
060 * the XDC tool.
061 *
062 * @author Jens Voß
063 * @since 0.5
064 * @version 0.5
065 */
066 public class XdcProcessor {
067
068 private static final Logger LOG = Logging.getLogger();
069
070 private static final String XSL_PKG = "net/sf/xdc/xsl";
071 private static final String RESOURCE_PKG = "net/sf/xdc/resources";
072
073 private CommandLine line;
074 private XdcSourceCollector sourceCollector;
075 private File outputDir;
076 private Properties baseProperties;
077 private Map tables; // Map<String, Properties>
078 private List patterns; // List<Pattern>
079 private SourceProcessor sourceProcessor;
080 private Charset encoding;
081 private Charset docencoding;
082 private Locale locale = Locale.US;
083
084 /**
085 * This public constructor uses the command line to collect all necessary
086 * sources for processing.
087 *
088 * @param line The command line which specifies which sources are to be
089 * processed and details on how they should be processed
090 */
091 public XdcProcessor(CommandLine line) {
092 this.line = line;
093 sourceCollector = new XdcSourceCollector(line);
094 if (line.hasOption("d")) {
095 outputDir = new File(line.getOptionValue("d"));
096 }
097 else {
098 outputDir = sourceCollector.getLeadingSourcePath();
099 }
100 if (!IOUtils.makeEmpty(outputDir)) {
101 LOG.warn("Output directory " + outputDir.getPath() + " could not be emptied.");
102 }
103 baseProperties = XdcOptions.getXdcOptions().getOptionProperties(line);
104 baseProperties.setProperty("framesetsize", String.valueOf(sourceCollector.getFramesetSize()));
105 tables = new HashMap(3);
106 tables.put("config", baseProperties);
107 if (line.hasOption("linksource")) {
108 sourceProcessor = new SourceProcessor(outputDir);
109 }
110 if (line.hasOption("encoding")) {
111 String value = line.getOptionValue("encoding");
112 try {
113 encoding = Charset.forName(value);
114 LOG.info("Using '" + value + "' for source file encoding.");
115 }
116 catch (UnsupportedCharsetException e) {
117 LOG.warn("Source file encoding '" + value + "' not recognized - using default.");
118 }
119 }
120 else {
121 LOG.info("No source file encoding specified - using default.");
122 }
123 if (line.hasOption("docencoding")) {
124 String value = line.getOptionValue("docencoding");
125 try {
126 docencoding = Charset.forName(value);
127 LOG.info("Using '" + value + "' for target file encoding.");
128 }
129 catch (UnsupportedCharsetException e) {
130 LOG.warn("Target file encoding '" + value + "' not recognized - using default.");
131 }
132 }
133 else {
134 if (encoding == null) {
135 LOG.info("No target file encoding specified - using default.");
136 }
137 else {
138 docencoding = encoding;
139 LOG.info("No target file encoding specified - using source file encoding (" + docencoding.name() + ").");
140 }
141 }
142 if (line.hasOption("locale")) {
143 StringTokenizer tok = new StringTokenizer(line.getOptionValue("locale"), "_", false);
144 switch (tok.countTokens()) {
145 case 1: locale = new Locale(tok.nextToken()); break;
146 case 2: locale = new Locale(tok.nextToken(), tok.nextToken()); break;
147 case 3: locale = new Locale(tok.nextToken(), tok.nextToken(), tok.nextToken()); break;
148 }
149 }
150 LOG.info("Using locale " + locale.toString());
151 Locale.setDefault(locale);
152 ResourceBundle bundle = ResourceBundle.getBundle("net.sf.xdc.resources.xdc", locale);
153 Properties dictionary = new Properties();
154 for (Enumeration keys = bundle.getKeys(); keys.hasMoreElements();) {
155 String key = (String) keys.nextElement();
156 dictionary.setProperty(key, bundle.getString(key));
157 }
158 tables.put("dictionary", dictionary);
159 patterns = new Vector();
160 }
161
162 /**
163 * This method generates all files for the XDC documentation pages.
164 */
165 public void process() {
166 processStylesheetFile();
167 processHelpFile();
168 preprocessBasicContent();
169 processBasicContent();
170 processSupportFiles();
171 }
172
173 private void preprocessBasicContent() {
174 XdcSource[] sources = sourceCollector.getXdcSources();
175 Set sourceFiles = new HashSet(); // Set<String>
176 for (int i = 0; i < sources.length; i++) {
177 XdcSource source = sources[i];
178 try {
179 Document doc = XslUtils.transform(source.getFile().getAbsolutePath(), encoding, XSL_PKG + "/patterns.xsl", tables);
180 NodeIterator iter = XPathUtils.selectNodes(doc, "//pattern");
181 Node patternNode;
182 while ((patternNode = iter.nextNode()) != null) {
183 String file = XPathUtils.selectNode(patternNode, "file/text()").getNodeValue();
184 String xpath = XmlUtils.getTextValue(XPathUtils.selectNode(patternNode, "xpath"));
185 XPathPattern pattern = new XPathPattern(file, xpath);
186 String sourceFile;
187 if (file == null || file.length() == 0) {
188 sourceFile = source.getSourceFileName();
189 }
190 else if (file.indexOf('/') < 0) {
191 sourceFile = source.getPackageName() + '/' + file;
192 }
193 else {
194 sourceFile = file;
195 }
196 pattern.setTargetFile(sourceFile);
197 sourceFiles.add(sourceFile);
198 patterns.add(pattern);
199 source.addPattern(pattern);
200 }
201 }
202 catch (XmlException e) {
203 LOG.error(e.getMessage(), e);
204 }
205 catch (TransformerException e) {
206 LOG.error(e.getMessage(), e);
207 }
208 }
209 for (Iterator iter = sourceFiles.iterator(); iter.hasNext();) {
210 String sourceFile = (String) iter.next();
211 for (Iterator pIter = patterns.iterator(); pIter.hasNext();) {
212 XPathPattern pattern = (XPathPattern) pIter.next();
213 if (pattern.getTargetFile().equals(sourceFile)) {
214 XdcSource source = sourceCollector.getXdcSource(sourceFile);
215 if (source == null) {
216 continue;
217 }
218 FileInputStream in = null;
219 try {
220 in = new FileInputStream(source.getFile());
221 Document doc = XmlUtils.parse(in);
222 Node node = XPathUtils.selectNode(doc, pattern.getPattern());
223 if (node != null) {
224 String link = XmlUtils.getLink((Element) node);
225 pattern.setValue(link);
226 }
227 }
228 catch (FileNotFoundException e) {
229 LOG.error(e.getMessage(), e);
230 }
231 catch (XmlException e) {
232 LOG.error(e.getMessage(), e);
233 }
234 catch (TransformerException e) {
235 LOG.error(e.getMessage(), e);
236 }
237 finally {
238 if (in != null) {
239 try {
240 in.close();
241 }
242 catch (IOException e) {
243 e.printStackTrace();
244 }
245 }
246 }
247 }
248 }
249 }
250 }
251
252 private void processBasicContent() {
253 String[] packageNames = sourceCollector.getPackageNames();
254 for (int i = 0; i < packageNames.length; i++) {
255
256 XdcSource[] sources = sourceCollector.getXdcSources(packageNames[i]);
257 File dir = new File(outputDir, packageNames[i]);
258 dir.mkdirs();
259 Set pkgDirs = new HashSet();
260
261 for (int j = 0; j < sources.length; j++) {
262 XdcSource source = sources[j];
263 // set the xpath patterns
264 Properties patterns = source.getPatterns();
265 if (patterns != null) {
266 tables.put("patterns", patterns);
267 }
268 pkgDirs.add(source.getFile().getParentFile());
269 DialectHandler dialectHandler = source.getDialectHandler();
270 Writer out = null;
271 try {
272 String sourceFilePath = source.getFile().getAbsolutePath();
273 source.setProcessingProperties(baseProperties,
274 j > 0 ? sources[j-1].getFileName() : null,
275 j < sources.length - 1 ? sources[j+1].getFileName() : null);
276 // set the root comment for use in package-summary page
277 String rootComment = XslUtils.transformToString(sourceFilePath,
278 XSL_PKG + "/source-header.xsl",
279 tables);
280 source.setRootComment(rootComment);
281 // then do the actual processing
282 Document htmlDoc = XslUtils.transform(sourceFilePath,
283 encoding,
284 dialectHandler.getXslResourcePath(),
285 tables);
286 if (LOG.isInfoEnabled()) {
287 LOG.info("Processing source file " + sourceFilePath);
288 }
289 out = IOUtils.getWriter(new File(dir, source.getFile().getName() + ".html"), docencoding);
290 XmlUtils.printHtml(out, htmlDoc);
291 if (sourceProcessor != null) {
292 sourceProcessor.processSource(source, encoding, docencoding);
293 }
294 XdcSource.clearProcessingProperties(baseProperties);
295 }
296 catch (XmlException e) {
297 LOG.error(e.getMessage(), e);
298 }
299 catch (IOException e) {
300 LOG.error(e.getMessage(), e);
301 }
302 finally {
303 if (out != null) {
304 try {
305 out.close();
306 }
307 catch (IOException e) {
308 LOG.error(e.getMessage(), e);
309 }
310 }
311 }
312 }
313 for (Iterator iter = pkgDirs.iterator(); iter.hasNext();) {
314 File docFilesDir = new File((File) iter.next(), "xdc-doc-files");
315 if (docFilesDir.exists() && docFilesDir.isDirectory()) {
316 File targetDir = new File(dir, "xdc-doc-files");
317 targetDir.mkdirs();
318 try {
319 IOUtils.copyTree(docFilesDir, targetDir);
320 }
321 catch (IOException e) {
322 LOG.error(e.getMessage(), e);
323 }
324 }
325 }
326 }
327 }
328
329 private void processSupportFiles() {
330
331 processAllclassesFrame();
332 processFrameset();
333
334 if (sourceCollector.getFramesetSize() == 3) {
335 processOverviewFiles();
336 }
337
338 String[] packageNames = this.sourceCollector.getPackageNames();
339 for (int i = 0; i < packageNames.length; i++) {
340 processPackageFiles(packageNames, i);
341 }
342
343 }
344
345 private void processStylesheetFile() {
346 InputStream in = null;
347 OutputStream out = null;
348 try {
349 String filename;
350 if (line.hasOption("stylesheetfile")) {
351 filename = line.getOptionValue("stylesheetfile");
352 in = new FileInputStream(filename);
353 filename = filename.replace('\\', '/');
354 filename = filename.substring(filename.lastIndexOf('/') + 1);
355 }
356 else {
357 ClassLoader cl = this.getClass().getClassLoader();
358 filename = "stylesheet.css";
359 in = cl.getResourceAsStream(RESOURCE_PKG + "/stylesheet.css");
360 }
361 baseProperties.setProperty("stylesheetfilename", filename);
362 File outFile = new File(outputDir, filename);
363 if (LOG.isInfoEnabled()) {
364 LOG.info("Processing generated file " + outFile.getAbsolutePath());
365 }
366 out = new FileOutputStream(outFile);
367 IOUtils.copy(in, out);
368 }
369 catch (FileNotFoundException e) {
370 LOG.error(e.getMessage(), e);
371 }
372 catch (IOException e) {
373 LOG.error(e.getMessage(), e);
374 }
375 finally {
376 if (in != null) {
377 try {
378 in.close();
379 }
380 catch (IOException e) {
381 LOG.error(e.getMessage(), e);
382 }
383 }
384 if (out != null) {
385 try {
386 out.close();
387 }
388 catch (IOException e) {
389 LOG.error(e.getMessage(), e);
390 }
391 }
392 }
393 }
394
395 private void processHelpFile() {
396 InputStream in = null;
397
398 if (line.hasOption("helpfile")) {
399 OutputStream out = null;
400 try {
401 String filename = line.getOptionValue("helpfile");
402 in = new FileInputStream(filename);
403 filename = filename.replace('\\', '/');
404 filename = filename.substring(filename.lastIndexOf('/') + 1);
405 File outFile = new File(outputDir, filename);
406 if (LOG.isInfoEnabled()) {
407 LOG.info("Processing generated file " + outFile.getAbsolutePath());
408 }
409 out = new FileOutputStream(outFile);
410 baseProperties.setProperty("helpfile", filename);
411 IOUtils.copy(in, out);
412 }
413 catch (FileNotFoundException e) {
414 LOG.error(e.getMessage(), e);
415 }
416 catch (IOException e) {
417 LOG.error(e.getMessage(), e);
418 }
419 finally {
420 if (in != null) {
421 try {
422 in.close();
423 }
424 catch (IOException e) {
425 LOG.error(e.getMessage(), e);
426 }
427 }
428 if (out != null) {
429 try {
430 out.close();
431 }
432 catch (IOException e) {
433 LOG.error(e.getMessage(), e);
434 }
435 }
436 }
437 return;
438 }
439
440 Properties props = new Properties(baseProperties);
441 ClassLoader cl = this.getClass().getClassLoader();
442 Document doc = null;
443 try {
444 in = cl.getResourceAsStream(RESOURCE_PKG + "/help-doc.xml");
445 doc = XmlUtils.parse(in);
446 }
447 catch (XmlException e) {
448 LOG.error(e.getMessage(), e);
449 }
450 finally {
451 if (in != null) {
452 try {
453 in.close();
454 }
455 catch (IOException e) {
456 LOG.error(e.getMessage(), e);
457 }
458 }
459 }
460 transformResource(doc, "help-doc.xsl", props, "help-doc.html");
461 baseProperties.setProperty("helpfile", "help-doc.html");
462 }
463
464 private void processAllclassesFrame() {
465 Properties props = new Properties(baseProperties);
466 try {
467 XdcSource[] sources = this.sourceCollector.getXdcSources();
468 Document xml = XmlUtils.createDocument();
469 Element sourcesNode = xml.createElement("sources");
470 xml.appendChild(sourcesNode);
471 for (int i = 0; i < sources.length; i++) {
472 Element sourceNode = xml.createElement("source");
473 sourceNode.setAttribute("name", sources[i].getFile().getName());
474 sourceNode.setAttribute("package", sources[i].getPackageName());
475 sourcesNode.appendChild(sourceNode);
476 }
477 transformResource(xml, "allclasses-frame.xsl", props, "allclasses-noframe.html");
478 props.setProperty("targetFrame", "classFrame");
479 transformResource(xml, "allclasses-frame.xsl", props, "allclasses-frame.html");
480 }
481 catch (XmlException e) {
482 LOG.error(e.getMessage(), e);
483 }
484 }
485
486 private void processFrameset() {
487 Properties props = new Properties(baseProperties);
488 String indexFileName = "index" + sourceCollector.getFramesetSize() + ".xsl";
489 if (sourceCollector.getFramesetSize() == 2) {
490 props.setProperty("package", sourceCollector.getPackageNames()[0]);
491 }
492 transformResource(indexFileName, props, "index.html");
493 }
494
495 private void processOverviewFiles() {
496 Properties props = new Properties(baseProperties);
497 props.setProperty("displayLevel", "overview");
498 props.setProperty("summaryText", sourceCollector.getSummaryText());
499 String[] packageNames = sourceCollector.getPackageNames();
500 try {
501 Document xml = XmlUtils.createDocument();
502 Element packagesNode = xml.createElement("packages");
503 xml.appendChild(packagesNode);
504 for (int i = 0; i < packageNames.length; i++) {
505 XdcPackage xdcPackage = sourceCollector.getXdcPackage(packageNames[i]);
506 Element packageNode = xml.createElement("package");
507 packageNode.setAttribute("name", packageNames[i]);
508 packagesNode.appendChild(packageNode);
509 packageNode.appendChild(xml.createTextNode(xdcPackage.getSummaryText()));
510 }
511 transformResource(xml, "overview-frame.xsl", props, "overview-frame.html");
512 transformResource(xml, "overview-summary.xsl", props, "overview-summary.html");
513 }
514 catch (XmlException e) {
515 LOG.error(e.getMessage(), e);
516 }
517 }
518
519 private void processPackageFiles(String[] packageNames, int i) {
520 XdcPackage xdcPackage = sourceCollector.getXdcPackage(packageNames[i]);
521 Properties props = new Properties(baseProperties);
522 props.setProperty("package", packageNames[i]);
523 props.setProperty("displayLevel", "package");
524 props.setProperty("summaryText", xdcPackage.getSummaryText());
525 if (i == 0) {
526 props.remove("prevFileName");
527 }
528 else {
529 props.setProperty("prevFileName", packageNames[i-1] + "/package-summary.html");
530 }
531 if (i == packageNames.length - 1) {
532 props.remove("nextFileName");
533 }
534 else {
535 props.setProperty("nextFileName", packageNames[i+1] + "/package-summary.html");
536 }
537 XdcSource[] sources = xdcPackage.getXdcSources();
538 try {
539 Document xml = XmlUtils.createDocument();
540 Element sourcesNode = xml.createElement("sources");
541 xml.appendChild(sourcesNode);
542 for (int j = 0; j < sources.length; j++) {
543 Element sourceNode = xml.createElement("source");
544 sourceNode.setAttribute("name", sources[j].getFile().getName());
545 sourcesNode.appendChild(sourceNode);
546 sourceNode.appendChild(xml.createTextNode(sources[j].getRootComment()));
547 }
548 transformResource(xml, "package-frame.xsl", props, packageNames[i] + "/package-frame.html");
549 transformResource(xml, "package-summary.xsl", props, packageNames[i] + "/package-summary.html");
550 }
551 catch (XmlException e) {
552 LOG.error(e.getMessage(), e);
553 }
554 }
555
556 private void transformResource(String resourceFileName,
557 Properties props, String outFileName) {
558 try {
559 transformResource(XmlUtils.createDocument(), resourceFileName, props, outFileName);
560 }
561 catch (XmlException e) {
562 LOG.error(e.getMessage(), e);
563 }
564 }
565
566 private void transformResource(Document xml, String resourceFileName,
567 Properties props, String outFileName) {
568 props.setProperty("outFileName", outFileName);
569 Writer out = null;
570 Properties oldConfig = (Properties) tables.put("config", props);
571 try {
572 Document html = XslUtils.transform(xml, XSL_PKG + '/' + resourceFileName, tables);
573 File outFile = new File(outputDir, outFileName);
574 outFile.getParentFile().mkdirs();
575 out = IOUtils.getWriter(outFile, docencoding);
576 if (LOG.isInfoEnabled()) {
577 LOG.info("Processing generated file " + outFile.getAbsolutePath());
578 }
579 XmlUtils.printHtml(out, html);
580 }
581 catch (FileNotFoundException e) {
582 LOG.error(e.getMessage(), e);
583 }
584 catch (IOException e) {
585 LOG.error(e.getMessage(), e);
586 }
587 catch (XmlException e) {
588 LOG.error(e.getMessage(), e);
589 }
590 finally {
591 if (out != null) {
592 try {
593 out.close();
594 }
595 catch (IOException e) {
596 LOG.error(e.getMessage(), e);
597 }
598 }
599 if (oldConfig != null) {
600 tables.put("config", oldConfig);
601 }
602 else {
603 tables.remove("config");
604 }
605 }
606 }
607
608 }